page.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import { useRouter, useParams } from 'next/navigation';
  4. import Link from 'next/link';
  5. import { fetchApi } from '@/lib/utils/client';
  6. import { useStudioContext } from '@/app/studio/context';
  7. import { useRankConfigContext } from '../../context';
  8. import { Separator } from '@/components/ui/separator';
  9. import RankPreviewPanel from '../../_components/RankPreviewPanel';
  10. import RankFormPanel from '../../_components/RankFormPanel';
  11. import { createEmptyForm, formatInput, parseInput } from '../../types';
  12. import type { FormState } from '../../types';
  13. import type { RankConfigItem } from '@/types/response/donation/rankConfig';
  14. export default function RankEditPage()
  15. {
  16. const router = useRouter();
  17. const { id } = useParams<{ id: string }>();
  18. const numericId = parseInt(id);
  19. const { channelID } = useStudioContext();
  20. const { items, loading, setSaving, fetchList } = useRankConfigContext();
  21. const [editingItem, setEditingItem] = useState<RankConfigItem|null>(null);
  22. const [form, setForm] = useState<FormState>(createEmptyForm());
  23. const [formInitialized, setFormInitialized] = useState(false);
  24. const [localSaving, setLocalSaving] = useState(false);
  25. // ── items 로드 후 form 초기화 ────────────────────
  26. useEffect(() => {
  27. if (formInitialized || items.length === 0) {
  28. return;
  29. }
  30. const found = items.find(item => item.id === numericId);
  31. if (found) {
  32. setEditingItem(found);
  33. setForm({
  34. title: found.title,
  35. theme: found.theme,
  36. period: found.period,
  37. startAt: found.startAt ? formatInput(new Date(found.startAt)) : null,
  38. endAt: found.endAt ? formatInput(new Date(found.endAt)) : null,
  39. isShowAmount: found.isShowAmount,
  40. maxRankCount: found.maxRankCount,
  41. nameMode: found.nameMode,
  42. isActive: found.isActive,
  43. titleFontFamily: found.titleFontFamily,
  44. titleFontSizePx: found.titleFontSizePx,
  45. titleFontColor: found.titleFontColor,
  46. nameDisplayType: found.nameDisplayType,
  47. isShowDonationCount: found.isShowDonationCount,
  48. isShowGradeIcon: found.isShowGradeIcon,
  49. isShowMemberIcon: found.isShowMemberIcon,
  50. rank1FontFamily: found.rank1FontFamily,
  51. rank1FontSizePx: found.rank1FontSizePx,
  52. rank1FontColor: found.rank1FontColor,
  53. rank2FontFamily: found.rank2FontFamily,
  54. rank2FontSizePx: found.rank2FontSizePx,
  55. rank2FontColor: found.rank2FontColor,
  56. rank3FontFamily: found.rank3FontFamily,
  57. rank3FontSizePx: found.rank3FontSizePx,
  58. rank3FontColor: found.rank3FontColor
  59. });
  60. setFormInitialized(true);
  61. } else if (!loading) {
  62. alert('순위 설정을 찾을 수 없습니다.');
  63. router.push('/studio/donation/rank/list');
  64. }
  65. }, [items, loading, numericId, formInitialized, router]);
  66. // ── 폼 필드 변경 ────────────────────────────────
  67. const handleFormChange = <K extends keyof FormState>(field: K, value: FormState[K]) => {
  68. setForm(prev => ({ ...prev, [field]: value }));
  69. };
  70. // ── 저장 ─────────────────────────────────────────
  71. const handleSave = async () => {
  72. if (!channelID || !editingItem) {
  73. return;
  74. }
  75. if (!form.title.trim()) {
  76. alert('제목을 입력해 주세요.');
  77. return;
  78. }
  79. setLocalSaving(true);
  80. setSaving(true);
  81. try {
  82. await fetchApi('/api/studio/donation/rank/config', {
  83. method: 'POST',
  84. body: {
  85. channelID,
  86. id: editingItem.id,
  87. ...form,
  88. startAt: form.startAt ? (parseInput(form.startAt) || undefined) : undefined,
  89. endAt: form.endAt ? (parseInput(form.endAt) || undefined) : undefined
  90. }
  91. });
  92. alert('수정되었습니다.');
  93. fetchList();
  94. router.push('/studio/donation/rank/list');
  95. } catch (err) {
  96. alert(err instanceof Error ? err.message : '저장에 실패했습니다.');
  97. } finally {
  98. setLocalSaving(false);
  99. setSaving(false);
  100. }
  101. };
  102. // ── 취소 ─────────────────────────────────────────
  103. const handleCancel = () => {
  104. router.push('/studio/donation/rank/list');
  105. };
  106. // ── 로딩 중 ──────────────────────────────────────
  107. if (!formInitialized) {
  108. return <div className="rank-config__loading">준비 중...</div>;
  109. }
  110. return (
  111. <>
  112. <div className="studio-page__title-row">
  113. <h1 className="studio-page__title">후원 순위 수정</h1>
  114. <Link href="/studio/donation/rank/list" className="rank-config__btn rank-config__btn--sm">< 목록으로</Link>
  115. </div>
  116. <div className="pt-5 pb-5">
  117. <Separator orientation="horizontal" />
  118. </div>
  119. <div className="rank-config__layout">
  120. <RankPreviewPanel form={form} />
  121. <Separator orientation="vertical" />
  122. <RankFormPanel
  123. form={form}
  124. editingItem={editingItem}
  125. saving={localSaving}
  126. onFormChange={handleFormChange}
  127. onSave={handleSave}
  128. onCancel={handleCancel}
  129. />
  130. </div>
  131. </>
  132. );
  133. }